•  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

State Machine(r1 Blame)

r1
r1

(새 문서)
1[include(틀:Solidity 디자인 패턴)]
2
3[[분류:블록체인]]
4[[분류:스마트 컨트랙트]]
5[[분류:Solidity]]
6[[분류:디자인 패턴]]
7
8{{{+2 '''State Machine 패턴'''}}}
9
10[목차]
11
12== 개요 ==
13State Machine 패턴은 컨트랙트의 생애 주기를 유한한 상태(state)들로 나누고, 각 상태에서 허용하는 행위와 전이(transition) 규칙을 명시적으로 정의하는 패턴이다.[*1 Wohrer, Maximilian; Zdun, Uwe (2018). [[https://ieeexplore.ieee.org/document/8327565|Smart Contracts: Security Patterns in the Ethereum Ecosystem and Solidity]]. ''2018 International Workshop on Blockchain Oriented Software Engineering (IWBOSE)''. IEEE. pp. 2–8.]
14
15컴퓨터 과학의 [[유한 상태 기계]](FSM) 이론에 기반한다.[*2 Hopcroft, John E.; Ullman, Jeffrey D. (1979). ''Introduction to Automata Theory, Languages, and Computation''. Addison-Wesley. ISBN 0-201-02988-X.]
16
17컨트랙트는 반드시 하나의 상태에만 존재하며 사전에 정의된 규칙에 따라서만 상태가 변경된다.
18
19== 동기 ==
20스마트 컨트랙트는 배포 후 코드 수정이 불가능하다. 그런데 경매, 에스크로, 거버넌스 등 대부분 온체인 비즈니스 로직은 '''여러 단계를 순서대로 거치는 생애주기'''를 가진다.
21
22경매에서 정산이 끝난 뒤 입찰을 넣거나 에스크로에서 입금 전에 물건 수령을 확인하는 것은 허용되면 안 된다.
23
24이러한 "잘못된 시점에 잘못된 함수가 호출되는" 버그는 전통 소프트웨어에서는 패치로 해결할 수 있지만 불변(immutable)한 스마트 컨트랙트에서는 자금 손실로 직결된다.[*3 Atzei, Nicola; Bartoletti, Massimo; Cimoli, Tiziana (2017). [[https://link.springer.com/chapter/10.1007/978-3-662-54455-6_8|A Survey of Attacks on Ethereum Smart Contracts (SoK)]]. ''Principles of Security and Trust''. Springer. pp. 164–186.]
25
26State Machine 패턴은 이 문제를 해결하기 위해 설계되었다.
27
28이 패턴이 해결하는 핵심 문제는 다음과 같다.
29
30 * '''단계 간 순서 강제''' - 정의되지 않은 경로로의 상태 전이를 구조적으로 차단한다.
31 * '''함수 호출 시점 제어''' - 각 함수가 실행 가능한 상태를 컴파일 타임에 선언하여, 잘못된 시점의 호출을 런타임에서 revert한다.
32 * '''시간 조건 자동 적용''' - 블록체인에 cron이 없는 환경에서 시간 경과에 따른 단계 전환을 트랜잭션 호출 시점에 자동 반영한다.
33 * '''감사 용이성''' - 상태와 전이를 다이어그램으로 시각화할 수 있어 코드 리뷰와 보안 감사가 용이해진다.[*1]
34
35== 구성 요소 ==
36
37=== 상태 정의 - enum ===
38{{{#!syntax solidity
39enum Stage {
40 Created,
41 Bidding,
42 Revealing,
43 Settled
44}
45Stage public currentStage = Stage.Created;
46}}}
47
48내부적으로 {{{uint8}}}로 저장된다. 정의되지 않은 값으로의 캐스팅은 revert된다. (Solidity 0.8+)[*4 Solidity Documentation. [[https://docs.soliditylang.org/en/latest/types.html#enums|Enums]]. docs.soliditylang.org.]
49
50=== 상태 검증 - modifier ===
51{{{#!syntax solidity
52modifier atStage(Stage expected) {
53 require(currentStage == expected, "Invalid stage");
54 _;
55}
56}}}
57
58함수에 {{{atStage(Stage.Bidding)}}}을 붙이면 Bidding 상태에서만 실행된다.
59
60=== 전이 함수 ===
61'''선형 전이''' - 순서대로만 진행:
62{{{#!syntax solidity
63function nextStage() internal {
64 currentStage = Stage(uint(currentStage) + 1);
65}
66}}}
67
68'''명시적 전이''' - 허용된 경로만:
69{{{#!syntax solidity
70function transitionTo(Stage next) internal {
71 require(allowedTransitions[currentStage][next]);
72 currentStage = next;
73}
74}}}
75
76=== 시간 기반 전이 - timedTransition ===
77블록체인에는 cron이 없으므로 다음 트랜잭션 호출 시 시간 경과를 검사한다.[*5 Solidity Documentation. [[https://docs.soliditylang.org/en/latest/common-patterns.html#state-machine|Common Patterns: State Machine]]. docs.soliditylang.org.]
78
79{{{#!syntax solidity
80modifier timedTransition() {
81 if (currentStage == Stage.Bidding
82 && block.timestamp >= createdAt + BIDDING_DURATION)
83 currentStage = Stage.Revealing;
84 if (currentStage == Stage.Revealing
85 && block.timestamp >= createdAt + BIDDING_DURATION + REVEAL_DURATION)
86 currentStage = Stage.Settled;
87 _;
88}
89}}}
90
91{{{#!wiki style="border-left: 3px solid #edab00; background-color: #fef6e7; padding: 10px 15px; margin: 10px 0;"
92'''주의''': {{{if}}}를 연속 사용한다({{{else if}}} 아님). 오랜 기간 트랜잭션이 없었을 때 한 번의 호출로 연쇄 전이가 가능해야 하기 때문이다.
93}}}
94
95=== 이벤트 ===
96{{{#!syntax solidity
97event StageChanged(Stage indexed from, Stage indexed to, address indexed triggeredBy);
98}}}
99
100모든 상태 전이에서 이벤트를 발행하여 오프체인 추적을 가능하게 한다.
101
102== 구현 패턴 ==
103
104=== 선형 전이 ===
105상태가 순차적으로만 진행되는 경우. {{{nextStage()}}}로 {{{uint(currentStage) + 1}}} 계산.
106
107경매, ICO, 투표, 타임락에 적합하다.
108
109=== 비선형 전이 - 전이 테이블 ===
110분기·복귀가 있는 워크플로우에서 사용한다. 허용된 전이를 mapping으로 정의한다.[*1]
111
112{{{#!syntax solidity
113mapping(Stage => mapping(Stage => bool)) private allowedTransitions;
114
115constructor() {
116 allowedTransitions[Stage.AwaitingPayment][Stage.Funded] = true;
117 allowedTransitions[Stage.Funded][Stage.Delivered] = true;
118 allowedTransitions[Stage.Funded][Stage.Disputed] = true;
119 allowedTransitions[Stage.Disputed][Stage.Delivered] = true; // resolve
120 allowedTransitions[Stage.Disputed][Stage.Cancelled] = true; // cancel
121}
122}}}
123
124=== 비트마스크 최적화 ===
125이중 mapping은 검증당 SLOAD 2회가 필요하다. 비트마스크를 사용하면 SLOAD 1회 + 비트 연산으로 줄일 수 있다.[*6 Ethereum Yellow Paper. [[https://ethereum.github.io/yellowpaper/paper.pdf|Ethereum: A Secure Decentralised Generalised Transaction Ledger]]. Appendix G. Fee Schedule.]
126
127{{{#!syntax solidity
128mapping(Stage => uint8) private transitionMap;
129
130// AwaitingPayment -> Funded(bit 1) 허용
131transitionMap[Stage.AwaitingPayment] = 1 << uint8(Stage.Funded);
132
133function transitionTo(Stage next) internal {
134 require(transitionMap[currentStage] & (1 << uint8(next)) != 0);
135 currentStage = next;
136}
137}}}
138
139||<tablealign=center><tablewidth=80%><tablebordercolor=#a2a9b1><tablebgcolor=#f8f9fa> '''방식''' || '''가스 비용 (검증 1회)''' || '''적합한 경우''' ||
140|| 이중 mapping || ~4,200 gas (SLOAD × 2) || 가독성 우선 ||
141|| 비트마스크 || ~2,100 gas (SLOAD × 1) || 호출 빈번, 가스 최적화 필요 ||
142|| 선형 nextStage() || ~200 gas (연산만) || 순차 전이만 있을 때 ||
143
144== 보안 고려사항 ==
145
146=== Reentrancy (재진입 공격) ===
147상태 전이 함수가 외부 컨트랙트에 ETH를 송금하거나 외부 함수를 호출할 때, 호출받은 컨트랙트가 원래 함수를 다시 호출(재진입)할 수 있다. 이때 상태가 아직 변경되지 않았다면, {{{atStage}}} modifier를 통과하여 동일한 함수가 반복 실행된다.[*3]
148
149대응은 Checks-Effects-Interactions(CEI) 패턴이다. 상태 변경(Effect)을 외부 호출(Interaction)보다 먼저 수행하면, 재진입 시 이미 변경된 상태에 의해 {{{atStage}}}에서 revert된다.[*7 ConsenSys Diligence. [[https://consensysdiligence.github.io/smart-contract-best-practices/attacks/reentrancy/|Reentrancy]]. ''Smart Contract Best Practices''.]
150
151=== block.timestamp 조작 ===
152시간 기반 전이({{{timedTransition}}})는 {{{block.timestamp}}}에 의존한다. 블록 제안자는 이 값을 실제 시간 대비 약 15초 범위 내에서 조작할 수 있다.[*8 ConsenSys Diligence. [[https://consensysdiligence.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/timestamp-dependence/|Timestamp Dependence]]. ''Smart Contract Best Practices''.] 따라서 각 단계의 최소 길이를 수 분 이상으로 설정해야 한다.
153
154=== Front-running ===
155상태 전이를 발생시키는 트랜잭션이 mempool에 공개되면, 공격자가 이를 관찰하고 더 높은 가스비로 자신의 트랜잭션을 먼저 포함시킬 수 있다.[*9 Daian, Philip et al. (2020). [[https://www.computer.org/csdl/proceedings-article/sp/2020/349700b910/1j2LgsZYg2A|Flash Boys 2.0]]. ''2020 IEEE Symposium on Security and Privacy''. pp. 910–927.] 대응으로는 commit-reveal 스킴이나 전이 전 최소 지연 시간을 두는 방법이 있다.
156
157== 활용 사례 ==
158||<tablealign=center><tablewidth=80%><tablebordercolor=#a2a9b1><tablebgcolor=#f8f9fa> '''분야''' || '''예시''' || '''상태 흐름''' ||
159|| DeFi 거버넌스 || Aave, Compound Governor || Pending → Active → Succeeded/Defeated → Queued → Executed ||
160|| NFT 민팅 || ERC-721 드롭 || Inactive → Whitelist → Public → SoldOut → Revealed ||
161|| 에스크로 || 결제 보호 || AwaitingPayment → Funded → Delivered → Complete ||
162|| 공급망 || 제품 추적 || Manufactured → Shipped → InTransit → Delivered → Verified ||
163|| 스테이킹 || PoS 프로토콜 || Unlocked → Staked → Cooldown → Withdrawable ||
164
165== 관련 패턴 ==
166 * [[Guard Check 패턴]] - {{{require}}}/{{{modifier}}}를 사용한 사전 조건 검증. State Machine의 {{{atStage}}}가 이에 해당한다.
167 * [[Access Restriction 패턴]] - 역할 기반 접근 제어. State Machine과 결합하여 "누가 + 언제" 조건을 완성한다.
168 * [[Checks-Effects-Interactions 패턴]] - 상태 변경 후 외부 호출. State Machine의 전이 보안에 필수적이다.
169 * [[Pull over Push 패턴]] - {{{withdraw()}}} 패턴. 상태 전이와 자금 인출을 분리하여 안전성을 높인다.
170
171== 관련 문서 ==
172 * [[블록체인]]
173 * [[스마트 컨트랙트]]
174 * [[Solidity]]
175 * [[이더리움]]
176 * [[유한 상태 기계]]
177 * [[디자인 패턴]]